package com.zuma.oci.util.function;

import cn.hutool.core.lang.Assert;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

public class SingletonSupplier<T> implements Supplier<T> {
    private final Supplier<? extends T> instanceSupplier;
    private final Supplier<? extends T> defaultSupplier;
    private volatile T singletonInstance;
    private final Lock writeLock = new ReentrantLock();

    public SingletonSupplier(T instance, Supplier<? extends T> defaultSupplier) {
        this.instanceSupplier = null;
        this.defaultSupplier = defaultSupplier;
        this.singletonInstance = instance;
    }

    public SingletonSupplier(Supplier<? extends T> instanceSupplier, Supplier<? extends T> defaultSupplier) {
        this.instanceSupplier = instanceSupplier;
        this.defaultSupplier = defaultSupplier;
    }

    private SingletonSupplier(Supplier<? extends T> supplier) {
        this.instanceSupplier = supplier;
        this.defaultSupplier = null;
    }

    private SingletonSupplier(T singletonInstance) {
        this.instanceSupplier = null;
        this.defaultSupplier = null;
        this.singletonInstance = singletonInstance;
    }

    public T get() {
        T instance = this.singletonInstance;
        if (instance == null) {
            this.writeLock.lock();

            try {
                instance = this.singletonInstance;
                if (instance == null) {
                    if (this.instanceSupplier != null) {
                        instance = (T) this.instanceSupplier.get();
                    }

                    if (instance == null && this.defaultSupplier != null) {
                        instance = (T) this.defaultSupplier.get();
                    }

                    this.singletonInstance = instance;
                }
            } finally {
                this.writeLock.unlock();
            }
        }

        return instance;
    }

    public T obtain() {
        T instance = (T) this.get();
        Assert.state(instance != null, "No instance from Supplier");
        return instance;
    }

    public static <T> SingletonSupplier<T> of(T instance) {
        return new SingletonSupplier<T>(instance);
    }

    public static <T> SingletonSupplier<T> ofNullable(T instance) {
        return instance != null ? new SingletonSupplier<T>(instance) : null;
    }

    public static <T> SingletonSupplier<T> of(Supplier<T> supplier) {
        return new SingletonSupplier<T>(supplier);
    }

    public static <T> SingletonSupplier<T> ofNullable(Supplier<T> supplier) {
        return supplier != null ? new SingletonSupplier<T>(supplier) : null;
    }
}